package com.zhuhjay.app.web.controller.monitor;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import com.zhuhjay.common.base.exception.BasicBusinessException;
import com.zhuhjay.common.base.util.BeanBuilder;
import com.zhuhjay.common.base.util.JacksonUtils;
import com.zhuhjay.common.core.util.SecurityContextHolder;
import com.zhuhjay.common.netty.handler.AbstractSocketHandler;
import com.zhuhjay.common.redis.util.RedisUtils;
import com.zhuhjay.common.security.LoginUserDetail;
import com.zhuhjay.common.web.model.vo.PageInfoVo;
import com.zhuhjay.service.system.event.UserOfflineEvent;
import com.zhuhjay.service.system.model.vo.ChatUserOnlineVo;
import com.zhuhjay.service.system.model.vo.MonitorUserOnlineVo;
import com.zhuhjay.service.system.service.SystemUserService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import java.util.*;
import java.util.stream.Collectors;

import static com.zhuhjay.common.base.constant.ApplicationConstant.USER_UNIQUE_ID_KEY;
import static com.zhuhjay.common.base.constant.CacheConstant.USER_ONLINE_PREFIX;
import static com.zhuhjay.common.base.constant.CacheConstant.USER_UNIQUE_ID_PREFIX;

/**
 * @author ZhuHJay
 * @date 2023/4/20 22:41
 */
@Api(tags = "在线用户")
@RestController
@RequestMapping("/online")
public class UserOnlineController {

    @Resource
    private SystemUserService userService;
    @Resource
    private ApplicationEventPublisher publisher;

    @ApiOperation("用户在线列表(监控)")
    @PreAuthorize("@perm.hasPerm('system:monitor:online:list')")
    @GetMapping("list")
    public PageInfoVo<MonitorUserOnlineVo> list(String username) {
        Set<String> keys = RedisUtils.getKeys(USER_ONLINE_PREFIX + "*");
        PageInfoVo<MonitorUserOnlineVo> page = new PageInfoVo<>();
        List<MonitorUserOnlineVo> list = new ArrayList<>();
        // 如果指定用户名, 则只查询指定用户名的在线用户
        if (StrUtil.isNotBlank(username)) {
            keys.removeIf(key -> !key.contains(username));
        }
        // 获取在线用户的所有key
        keys.forEach(key -> {
            // 获取在线用户的所有uid
            RedisUtils.getMembers4Set(key).forEach(uid -> {
                // 获取缓存中的uid用户信息
                String infoStr = RedisUtils.getCache(USER_UNIQUE_ID_PREFIX + uid);
                // 如果用户信息为空, 则表示用户连接不存在了, 移除在线用户
                if (infoStr == null) {
                    RedisUtils.removeValue4Set(key, uid);
                } else {
                    LoginUserDetail loginUserDetail = JacksonUtils.readValue(infoStr, LoginUserDetail.class);
                    MonitorUserOnlineVo userOnlineVo = BeanUtil.copyProperties(
                            loginUserDetail, MonitorUserOnlineVo.class, USER_UNIQUE_ID_KEY);
                    userOnlineVo.setUid(uid);
                    list.add(userOnlineVo);
                }
            });
        });
        page.setTotal(list.size());
        page.setRecords(list);
        return page;
    }

    @ApiOperation("强制下线")
    @PreAuthorize("@perm.hasPerm('system:monitor:online:offline')")
    @DeleteMapping("offline/{username}/{uid}")
    public void offline(@PathVariable String username, @PathVariable String uid) {
        // 不能强制下线自己
        String currentUid = String.valueOf(SecurityContextHolder.getLoginUserDetail().getUid());
        if (currentUid.equals(uid)) {
            throw new BasicBusinessException("不能强制下线自己");
        }
        // 触发下线事件(移除单个连接)
        publisher.publishEvent(new UserOfflineEvent(this, username, uid));
    }

    @ApiOperation("用户在线列表(聊天)")
    @GetMapping("chat/list")
    public List<ChatUserOnlineVo> listChat() {
        Set<String> uids = AbstractSocketHandler.UID_SESSION_MAPPING.keySet();
        // 获取所有在线用户的信息(一个用户只存储一次)
        Map<String, LoginUserDetail> map = new HashMap<>(uids.size());
        uids.forEach(uid -> {
            String infoStr = RedisUtils.getCache(USER_UNIQUE_ID_PREFIX + uid);
            if (infoStr != null) {
                LoginUserDetail userDetail = JacksonUtils.readValue(infoStr, LoginUserDetail.class);
                map.putIfAbsent(userDetail.getUsername(), userDetail);
            }
        });
        return map.values().stream().sorted(Comparator.comparing(LoginUserDetail::getLoginTime).reversed())
                .map(userDetail -> BeanBuilder.builder(ChatUserOnlineVo::new)
                        .with(ChatUserOnlineVo::setUsername, userDetail.getUsername())
                        .with(ChatUserOnlineVo::setAvatar, userService.getAvatar(userDetail.getUsername()))
                        .build()
                ).collect(Collectors.toList());
    }

}
